package com.chatplus.application.web.interceptor;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.chatplus.application.common.logging.SouthernQuietLogger;
import com.chatplus.application.common.logging.SouthernQuietLoggerFactory;
import com.chatplus.application.constant.Constants;
import com.chatplus.application.web.auditlogger.AuditLogsEntity;
import com.chatplus.application.web.auditlogger.UrlFilterUtil;
import com.chatplus.application.web.satoken.helper.LoginHelper;
import com.chatplus.application.web.util.IpUtil;
import com.chatplus.application.web.util.ParameterUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.time.Instant;
import java.util.Map;

/**
 * 审计日志拦截器
 *
 * @author chj
 * @date 2022/6/14
 **/
public class AuditLoggerInterceptor implements HandlerInterceptor {

    private static final SouthernQuietLogger LOGGER = SouthernQuietLoggerFactory.getLogger(AuditLoggerInterceptor.class);
    //请求开始时间标识
    private static final String LOGGER_SEND_TIME = "AUDIT_LOGGER_SEND_TIME";
    //请求日志实体标识
    private static final String LOGGER_ACCESSING = "AUDIT_LOGGER_ACCESSING_ENTITY";
    // 响应体标识
    public static final String AUDIT_LOGGER_BODY = "AUDIT_LOGGER_BODY";
    public static final String APPLICATION_JSON = "application/json";
    public static final String TEXT_PLAIN = "text/plain";
    public static final String TEXT_HTML = "text/html";
    private ObjectMapper mapper;

    private UrlFilterUtil urlFilterUtil;
    private Environment env;

    @Autowired
    public void setMapper(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Autowired
    public void setUrlFilterUtil(UrlFilterUtil urlFilterUtil) {
        this.urlFilterUtil = urlFilterUtil;
    }

    @Autowired
    public void setEnv(Environment env) {
        this.env = env;
    }

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
        if (!urlFilterUtil.excludesURL(request)) {
            try {
                long start = System.currentTimeMillis();
                String contentType = request.getContentType();
                AuditLogsEntity auditLogsEntity = new AuditLogsEntity();
                // 流数据获取，比如 json
                if (request instanceof IContentCachingRequestWrapper requestWrapper) {
                    String body = new String(requestWrapper.getBody(), request.getCharacterEncoding());
                    auditLogsEntity.setParameters(body);
                } else {
                    Map<String, String> parameters = ParameterUtils.toMap(request);
                    auditLogsEntity.setParameters(parameters.toString());
                }
                // 如果是移动端的话最好传客户端操作系统和版本号
                auditLogsEntity.setClientId(request.getHeader(Constants.CLIENT_ID_HEADER));
                auditLogsEntity.setAppVersion(request.getHeader(Constants.CLIENT_APP_VERSION));
                auditLogsEntity.setSource(env.getProperty("spring.application.name"));
                auditLogsEntity.setContentType(contentType);
                auditLogsEntity.setClientIpAddress(IpUtil.getRemoteIp(request));
                auditLogsEntity.setExecutionTime(Instant.now());
                auditLogsEntity.setUrl(request.getRequestURI());
                auditLogsEntity.setHttpMethod(request.getMethod());
                //打印method的相关信息
                if (handler instanceof HandlerMethod handlerMethod) {
                    // 获取处理当前请求的 handler 信息
                    auditLogsEntity.setServiceName(handlerMethod.getBeanType().getName());
                    auditLogsEntity.setMethodName(handlerMethod.getMethod().getName());
                }
                //获取浏览器信息
                String uaStr = request.getHeader("user-agent");
                if (!ObjectUtils.isEmpty(uaStr)) {
                    UserAgent ua = UserAgentUtil.parse(uaStr);
                    auditLogsEntity.setBrowserInfo(uaStr);
                    auditLogsEntity.setDeviceName(ua.getPlatform().toString());
                }
                //设置请求实体到request内，方便afterCompletion方法调用
                request.setAttribute(LOGGER_ACCESSING, auditLogsEntity);
                request.setAttribute(LOGGER_SEND_TIME, start);
            } catch (Exception e) {
                LOGGER.message("审计日志前置拦截器异常").exception(e).error();
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
        try {
            if (urlFilterUtil.excludesURL(request)) {
                return;
            }
            long start = Long.parseLong(request.getAttribute(LOGGER_SEND_TIME).toString());
            long executionDuration = System.currentTimeMillis() - start;
            //获取本次请求日志实体
            AuditLogsEntity auditLogsEntity = (AuditLogsEntity) request.getAttribute(LOGGER_ACCESSING);
            if (ex != null) {
                auditLogsEntity.setException(ex.getMessage());
            }
            Object body = request.getAttribute(AUDIT_LOGGER_BODY);
            if (body != null) {
                String responseBody = mapper.writeValueAsString(body);
                auditLogsEntity.setResponseBody(responseBody);
            }
            auditLogsEntity.setExecutionDuration(executionDuration);

            int status = response.getStatus();
            //已知异常在BaseExceptionAdvice处理了，未知异常没处理，所以这里要特殊处理下
            if (ex != null && HttpStatus.OK.value() == status) {
                status = HttpStatus.INTERNAL_SERVER_ERROR.value();
            }
            auditLogsEntity.setHttpStatus(status);
            auditLogsEntity.setUserId(LoginHelper.getUserId());
            // 先打印日志，后续有空再保存到数据库
            LOGGER.message(mapper.writeValueAsString(auditLogsEntity)).info();
        } catch (Exception e) {
            LOGGER.message("审计日志后置拦截器异常").exception(e).error();
        }
    }
}
